package com.gdust.hzx.manager.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.gdust.hzx.manager.mapper.SysRoleUserMapper;
import com.gdust.hzx.manager.mapper.SysUserMapper;
import com.gdust.hzx.manager.service.SysUserService;
import com.gdust.spzx.common.exception.GdustException;
import com.gdust.hzx.model.dto.system.AssginRoleDto;
import com.gdust.hzx.model.dto.system.LoginDto;
import com.gdust.hzx.model.dto.system.SysUserDto;
import com.gdust.hzx.model.entity.system.SysUser;
import com.gdust.hzx.model.vo.common.ResultCodeEnum;
import com.gdust.hzx.model.vo.system.LoginVo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * ClassName: SysUserServiceImpl
 * Package: com.gdust.spzx.service.impl
 * Description:用户表实现类
 *
 * @Author syun
 * @Create 2024/11/2 22:19
 * @Version 1.0
 */
@Service
public class SysUserServiceImpl implements SysUserService {

    //注入用户表的mapper
    @Autowired
    private SysUserMapper sysUserMapper;

    //注入Redis,注：验证码的key和value都设置泛型为string
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    //注入用户角色表的mapper
    @Autowired
    private SysRoleUserMapper sysRoleUserMapper;

    /**
     *  //用户登录接口
     *  //LoginDto:用户登录请求参数,包含：用户名、密码、提交验证码、验证码key
     * @param loginDto
     * @return
     */
    @Override
    public LoginVo login(LoginDto loginDto) {
        //验证码校验部分(后添加)
        //1.获取输入验证码和存储到redis的key名称  loginDto获取到
        String captcha = loginDto.getCaptcha();//获取提交验证码
        String key = loginDto.getCodeKey();//获取验证码key

        //2.根据获取的Redis里面的key,查询Redis里面存储验证码
        // 在验证码实现类中的set格式：("user:validate"+key
        //V get(Object key);
        String redisCode = redisTemplate.opsForValue().get("user:validate" + key);

        //3.比较输入的验证码和Redis存储验证码是否一致
        if(StrUtil.isEmpty(redisCode) || !StrUtil.equalsIgnoreCase(redisCode,captcha)) {
            //4 如果不一致，提示用户，校验失败
            throw new GdustException(ResultCodeEnum.VALIDATECODE_ERROR);
        }

        //5.如果一致，则删除Redis里面的验证码
        redisTemplate.delete("user:validate"+key);


        //用户登录部分
        //1.获取提交用户名，loginDto获取到
        String userName = loginDto.getUserName();

        //2.根据用户名查询用户 sys_User表
        SysUser sysUser = sysUserMapper.selectUserInfoByUserName(userName);

       //3.如果根据用户名查不到相应信息，则用户不存在，返回错误信息
        if(sysUser == null){
//            throw new RuntimeException("用户不存在");
            //抛出自定义异常：账户名或密码错误
            throw new GdustException(ResultCodeEnum.LOGIN_ERROR);
        }

        //4.如果根据用户名查询到对应用户信息，则用户存在
        //5.获取输入的密码，比较输入的密码和数据库密码是否一致
        //5.1 数据库密码
        String database_password = sysUser.getPassword();
        //输入的密码:对用户输入的密码进行 MD5 加密
        String input_password = DigestUtils.md5DigestAsHex(loginDto.getPassword().getBytes());

        //5.2 比较两个密码一致性
        if(!input_password.equals(database_password)){
//            throw new RuntimeException("密码不正确");
            //抛出自定义异常：账户名或密码错误
            throw new GdustException(ResultCodeEnum.LOGIN_ERROR);
        }

        //6.如果密码一致，则登录成功，如果密码不一致则登录失败
        //7.登录成功，生成用户唯一表示token
        //生成一个不带连字符（-）的UUID字符串
        String token = UUID.randomUUID().toString().replaceAll("-", "");

        //8.把登录成功用户信息放到Redis中
        // key : token   value: 用户信息
        redisTemplate.opsForValue()
                .set("user:login"+ token,
                        JSON.toJSONString(sysUser),
                        7,
                        TimeUnit.DAYS
                );

        //9.返回loginVo对象
        LoginVo loginVo = new LoginVo();//新建VO对象
        loginVo.setToken(token);//放入token
        return loginVo;//返回vo对象
    }

    /**
     * //获取用户信息
     *
     * 根据提供的token从Redis中获取已登录用户的信息。
     * 将获取到的用户信息JSON字符串解析为SysUser对象并返回。
     * @param token
     * @return
     */
    @Override
    public SysUser getUserInfo(String token) {
        //从Redis中获取用户信息
        String userJson = redisTemplate.opsForValue().get("user:login" + token);

        // 将JSON字符串解析为SysUser对象
        SysUser sysUser = JSON.parseObject(userJson, SysUser.class);

        //返回SysUser对象
        return sysUser;
    }

    /**
     * 用户退出功能
     *
     * 后端根据token从Redis中删除用户数据，删除该缓存
     * @param token
     */
    @Override
    public void logout(String token) {
        //删除对应的用户信息
        redisTemplate.delete("user:login" + token);
    }

    /**
     * 用户条件分页查询接口
     *
     * 该方法实现了基于条件的分页查询功能，通过 PageHelper 分页插件实现分页控制，
     * 并调用 Mapper 层方法获取符合条件的用户数据。
     *
     * @param pageNum 当前页，用于指定查询的起始页
     * @param pageSize 每页记录数，用于控制分页返回的记录数量
     * @param sysUserDto 请求参数实体类，包含搜索条件，如关键字、开始时间、结束时间等
     * @return 分页信息对象 PageInfo<SysUser>，包含分页的结果集及相关分页信息
     */
    @Override
    public PageInfo<SysUser> findByPage(Integer pageNum, Integer pageSize, SysUserDto sysUserDto) {
        // 设置分页参数，指定当前页和每页记录数
        PageHelper.startPage(pageNum, pageSize);

        // 调用 Mapper 层方法，根据条件获取符合条件的用户列表
        List<SysUser> list = sysUserMapper.findByPage(sysUserDto);

        // 构造分页信息对象，将查询到的列表封装到分页结果中
        PageInfo<SysUser> pageInfo = new PageInfo<>(list);

        // 返回分页信息对象
        return pageInfo;
    }

    /**
     * 添加用户接口
     * @param sysUser 系统用户实体类
     */
    @Override
    public void saveSysUser(SysUser sysUser) {
        //1.判断用户名不能重复
        String userName = sysUser.getUserName();//获取用户名
        SysUser dbSysUser = sysUserMapper.selectUserInfoByUserName(userName);//根据用户名查询用户
        //查询用户结果不为空
        if(dbSysUser != null){
            //抛出自定义异常：用户名已经存在
            throw new GdustException(ResultCodeEnum.USER_NAME_IS_EXISTS);
        }


        //2.输入密码进行加密
        String md5_password = DigestUtils.md5DigestAsHex(sysUser.getPassword().getBytes());
        sysUser.setPassword(md5_password);//设置密码


        //3.设置status值 1为可用，0为不可用
        sysUser.setStatus(1);

        //添加用户
        sysUserMapper.save(sysUser);
    }

    /**
     * 添加用户接口
     * @param sysUser 系统用户实体类
     */
    @Override
    public void updateSysUser(SysUser sysUser) {
        sysUserMapper.update(sysUser);
    }

    /**
     * 用户删除
     * @param userId 用户id
     */
    @Override
    public void deleteById(Long userId) {
        sysUserMapper.deleteById(userId);
    }

    /**
     * 用户分配角色：保存分配数据
     *
     * 功能描述：
     * 1. 清空用户之前分配的角色数据。
     * 2. 根据传入的新角色ID列表重新分配角色。
     *
     * 注意事项：
     * - 采用“先删除后新增”的方式确保角色分配的完整性。
     * - 使用了事务注解 `@Transactional`，保证操作的原子性，若任意一步失败，所有操作将回滚。
     *
     * @param assginRoleDto 请求参数实体类，包含用户ID和分配的角色ID列表
     */
    @Transactional
    @Override
    public void doAssign(AssginRoleDto assginRoleDto) {
        // 1. 根据用户ID删除用户之前分配的角色数据
        // 调用 Mapper 层方法，通过用户ID删除关联的角色记录
        sysRoleUserMapper.deleteByUserId(assginRoleDto.getUserId());

        // 2. 获取新的角色ID列表，并重新分配角色
        List<Long> roleIdList = assginRoleDto.getRoleIdList(); // 从 DTO 中获取角色ID列表

        // 遍历角色ID列表，逐一分配角色
        for (Long roleId : roleIdList) {
            // 调用 Mapper 层方法，为当前用户新增角色分配记录
            sysRoleUserMapper.doAssign(assginRoleDto.getUserId(), roleId);
        }
        // 操作完成后，新的角色分配已生效
    }



}
